﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Collections;

namespace be.metanous.mvvm.collections
{
    public class ObservableItemsCollection<T>: DispatcherObservableCollection<T> where T : INotifyPropertyChanged
    {
        /// <summary> 
        /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. 
        /// </summary> 
        public ObservableItemsCollection()
        {
        }

        /// <summary> 
        /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. 
        /// </summary> 
        /// <param name="collection">collection: The collection from which the elements are copied.</param> 
        /// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception> 
        public ObservableItemsCollection(IEnumerable<T> collection)
        {
            AddRange(collection);
        }

        /// <summary> 
        /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
        /// </summary> 
        public override void AddRange(IEnumerable<T> collection)
        {
            foreach (var i in collection) Items.Add(i);

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)Items));
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);
        }

        protected override void ClearItems()
        {
            foreach (INotifyPropertyChanged item in Items)
            {
                if (item != null)
                    item.PropertyChanged -= Item_PropertyChanged;
            }

            base.ClearItems();
        }

        /// <summary> 
        /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). 
        /// </summary> 
        public void RemoveRange(IEnumerable<T> collection)
        {
            foreach (var i in collection) Items.Remove(i);

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)Items));
        }

        /// <summary> 
        /// Clears the current collection and replaces it with the specified item. 
        /// </summary> 
        public void Replace(T item)
        {
            ReplaceRange(new[] {item});
        }

        /// <summary> 
        /// Clears the current collection and replaces it with the specified collection. 
        /// </summary> 
        public void ReplaceRange(IEnumerable<T> collection)
        {
            var old = new List<T>(Items);
            Items.Clear();
            foreach (var i in collection) Items.Add(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, old, (IList)Items));
        }

        /// <summary> 
        /// Makes sure the event handlers to the items are set or cleared. 
        /// </summary> 
        private static void ObservableItemsCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null){
                foreach (INotifyPropertyChanged item in e.OldItems)
                {
                    if (item != null)
                        item.PropertyChanged -= ((ObservableItemsCollection<T>)sender).Item_PropertyChanged;
                }
            }

            if (e.NewItems!= null){
                foreach (INotifyPropertyChanged item in e.NewItems)
                {
                    if (item != null)
                        item.PropertyChanged += ((ObservableItemsCollection<T>)sender).Item_PropertyChanged;
                }
            }
        }

        private PropertyChangedEventHandler _itemPropertyChanged;

        /// <summary> 
        /// The ItemPropertyChanged event handler that listens to property changes
        /// of items contained in this collection.
        /// </summary> 
        ///
        public event PropertyChangedEventHandler ItemPropertyChanged
        {
            add
            {
                _itemPropertyChanged = (PropertyChangedEventHandler) Delegate.Combine(_itemPropertyChanged, value);
                if (_itemPropertyChanged != null && _itemPropertyChanged.GetInvocationList().Length != 0)
                {
                    foreach (INotifyPropertyChanged item in Items)
                    {
                        if (item != null)
                            item.PropertyChanged += Item_PropertyChanged;
                    }

                    // start observing children
                    CollectionChanged += ObservableItemsCollectionCollectionChanged;
 
                }
            }
            remove
            {
                _itemPropertyChanged = (PropertyChangedEventHandler)Delegate.Remove(_itemPropertyChanged, value);
                if (_itemPropertyChanged == null ||  _itemPropertyChanged.GetInvocationList().Length == 0)
                {

                    // stop observing children
                    CollectionChanged -= ObservableItemsCollectionCollectionChanged;

                    foreach (INotifyPropertyChanged item in Items)
                    {
                        if (item != null)
                            item.PropertyChanged -= Item_PropertyChanged;
                    }
                }
            }
        }

        /// <summary> 
        /// Notifies the ItemPropertyChanged handler when a contained item's property changes.
        /// </summary> 
        private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (_itemPropertyChanged != null)
                _itemPropertyChanged(sender, e);
        }
    }
}
